use std::os::unix::ffi::OsStrExt as _;

use crate::{runtime::FuncBinds, Env, Value};

use super::Builtin;

pub fn bind(env: &mut FuncBinds) {
    env.bind_builtin(
        "print",
        Print {
            stdout: true,
            newline: false,
        },
    );

    env.bind_builtin(
        "println",
        Print {
            stdout: true,
            newline: true,
        },
    );

    env.bind_builtin(
        "eprint",
        Print {
            stdout: false,
            newline: false,
        },
    );

    env.bind_builtin(
        "eprintln",
        Print {
            stdout: false,
            newline: true,
        },
    );
}

struct Print {
    stdout: bool,
    newline: bool,
}

impl Builtin for Print {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        let mut w: &mut dyn std::io::Write;
        let mut stdout;
        let mut stderr;

        if self.stdout {
            stdout = std::io::stdout().lock();
            w = &mut stdout;
        } else {
            stderr = std::io::stderr().lock();
            w = &mut stderr;
        }

        for arg in argv {
            print_val(arg, &mut w)?;
        }

        if self.newline {
            writeln!(w).map_err(|err| eprintln!("io error: {err}"))?;
        }

        Ok(Value::Integer(0))
    }
}

fn print_val<W: std::io::Write>(value: &Value, w: &mut W) -> Result<(), ()> {
    print_val_io(value, w).map_err(|err| eprintln!("io error: {err}"))
}

fn print_val_io<W: std::io::Write>(value: &Value, w: &mut W) -> std::io::Result<()> {
    match value {
        Value::Symbol(x) | Value::String(x) => write!(w, "{x}")?,
        Value::Integer(x) => write!(w, "{x}")?,
        Value::Bool(x) => write!(w, "{}", if *x { "True" } else { "False" })?,

        Value::Path(p) => w.write_all(p.as_os_str().as_bytes())?,
        Value::OsString(os) => w.write_all(os.as_bytes())?,

        Value::Array(a) => {
            for each in a {
                print_val_io(each, w)?;
            }
        }
        Value::Hash(hash) => {
            write!(w, "{{")?;

            let mut first = true;
            for (name, value) in hash {
                if first {
                    first = false;
                } else {
                    write!(w, ", ")?;
                }

                write!(w, "{}: ", name)?;
                print_val_io(value, w)?;
            }

            write!(w, "}}")?;
        }
    }

    Ok(())
}
